﻿using System;
using System.Collections.Generic;
using System.Threading;
using Autofac;
using Autofac.Builder;
using Autofac.Core;
using Autofac.Features.ResolveAnything;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Ioc;
using MugenMvvmToolkit.Ioc.Parameters;
using MugenMvvmToolkit.Utils;

// ReSharper disable CheckNamespace

namespace MugenMvvmToolkit
// ReSharper restore CheckNamespace
{
    /// <summary>
    ///     Represents the Autofac ioc adapter.
    /// </summary>
    public class AutofacIocAdapter : IIocAdapter
    {
        #region Fields

        private static int _idCounter;
        private readonly ILifetimeScope _container;

        #endregion

        #region Constructor

        /// <summary>
        ///     Initializes a new instance of the <see cref="AutofacIocAdapter" /> class.
        /// </summary>
        public AutofacIocAdapter(ILifetimeScope container, IIocAdapter parent = null)
        {
            Should.NotBeNull(container, "container");
            _container = container;
            Parent = parent;
            Container = _container;
            var containerBuilder = new ContainerBuilder();
            _container = container;
            containerBuilder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource());
            containerBuilder.Update(container.ComponentRegistry);
            Id = Interlocked.Increment(ref _idCounter);
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        ///     Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            _container.Dispose();
            IsDisposed = true;
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Converts parameters.
        /// </summary>
        private static Parameter[] ConvertParameters(IList<IIocParameter> parameters)
        {
            if (parameters == null || parameters.Count == 0) return new Parameter[0];
            var result = new List<Parameter>();
            foreach (IIocParameter iocParameterBase in parameters)
            {
                switch (iocParameterBase.ParameterType)
                {
                    case ParameterType.Constructor:
                        result.Add(new NamedParameter(iocParameterBase.Name, iocParameterBase.Value));
                        break;
                    case ParameterType.Property:
                        result.Add(new NamedPropertyParameter(iocParameterBase.Name, iocParameterBase.Value));
                        break;
                }
            }
            return result.ToArray();
        }

        private static void SetLifetimeScope(DependencyLifecycle dependencyLifecycle,
            IRegistrationBuilder
                <object, ConcreteReflectionActivatorData, SingleRegistrationStyle>
                registrationBuilder)
        {
            switch (dependencyLifecycle)
            {
                case DependencyLifecycle.TransientScopeInstance:
                    registrationBuilder.InstancePerDependency();
                    break;
                case DependencyLifecycle.SingleInstance:
                    registrationBuilder.SingleInstance();
                    break;
                default:
                    Thrower.EnumOutOfRange("dependencyLifecycle", dependencyLifecycle);
                    break;                    
            }
        }

        #endregion

        #region Implementation of IIocAdapter

        /// <summary>
        ///     Gets the id of <see cref="IIocAdapter" />.
        /// </summary>
        public int Id { get; private set; }

        /// <summary>
        ///     Gets a value indicating whether this instance is disposed.
        /// </summary>
        public bool IsDisposed { get; private set; }

        /// <summary>
        ///     Gets the parent ioc adapter.
        /// </summary>
        public IIocAdapter Parent { get; private set; }

        /// <summary>
        ///     Gets the original ioc container.
        /// </summary>
        public object Container { get; private set; }

        /// <summary>
        ///     Creates a child ioc adapter.
        /// </summary>
        /// <returns>
        ///     An instance of <see cref="IIocAdapter" />.
        /// </returns>
        public IIocAdapter CreateChild()
        {
            this.NotBeDisposed();
            return new AutofacIocAdapter(_container.BeginLifetimeScope(), this);
        }

        /// <summary>
        ///     Gets an instance of the specified service.
        /// </summary>
        /// <param name="service">The specified service type.</param>
        /// <param name="name">The specified binding name.</param>
        /// <param name="parameters">The specified parameters.</param>
        /// <returns>An instance of the service.</returns>
        public object Get(Type service, string name = null, params IIocParameter[] parameters)
        {
            this.NotBeDisposed();
            Should.NotBeNull(service, "service");
            if (name == null)
                return _container.Resolve(service, ConvertParameters(parameters));
            return _container.ResolveNamed(name, service, ConvertParameters(parameters));
        }

        /// <summary>
        ///     Gets all instances of the specified service.
        /// </summary>
        /// <param name="service">Specified service type.</param>
        /// <param name="name">The specified binding name.</param>
        /// <param name="parameters">The specified parameters.</param>
        /// <returns>An instance of the service.</returns>
        public IEnumerable<object> GetAll(Type service, string name = null, params IIocParameter[] parameters)
        {
            this.NotBeDisposed();
            Should.NotBeNull(service, "service");
            if (name == null)
                return
                    (IEnumerable<object>)
                        _container.Resolve(typeof(IEnumerable<>).MakeGenericType(service),
                            ConvertParameters(parameters));
            return
                (IEnumerable<object>)
                    _container.ResolveNamed(name, typeof(IEnumerable<>).MakeGenericType(service),
                        ConvertParameters(parameters));
        }

        /// <summary>
        ///     Indicates that the service should be bound to the specified constant value.
        /// </summary>
        public void BindToConstant(Type service, object constValue, string name = null)
        {
            this.NotBeDisposed();
            Should.NotBeNull(service, "service");
            var builder = new ContainerBuilder();
            if (name == null)
                builder.RegisterInstance(constValue).As(service);
            else
                builder.RegisterInstance(constValue).Named(name, service);
            builder.Update(_container.ComponentRegistry);
        }

        /// <summary>
        ///     Indicates that the service should be bound to the specified type.
        /// </summary>
        /// <param name="service">The specified service type.</param>
        /// <param name="typeTo">The specified to type</param>
        /// <param name="name">The specified binding name.</param>
        /// <param name="dependencyLifecycle">
        ///     The specified <see cref="DependencyLifecycle" />
        /// </param>
        public void Bind(Type service, Type typeTo, DependencyLifecycle dependencyLifecycle, string name = null)
        {
            this.NotBeDisposed();
            Should.NotBeNull(service, "service");
            Should.NotBeNull(typeTo, "typeTo");
            var builder = new ContainerBuilder();
            var syntax = name == null
                ? builder.RegisterType(typeTo).As(service)
                : builder.RegisterType(typeTo).Named(name, service);
            SetLifetimeScope(dependencyLifecycle, syntax);
            builder.Update(_container.ComponentRegistry);
        }

        /// <summary>
        ///     Unregisters all bindings with specified conditions for the specified service.
        /// </summary>
        /// <param name="service">The specified service type.</param>
        public void Unbind(Type service)
        {
            Should.MethodBeSupported(false, "Unbind(Type service)");
        }

        /// <summary>
        ///     Determines whether the specified request can be resolved.
        /// </summary>
        /// <param name="service">The specified service type.</param>
        /// <returns>
        ///     <c>True</c> if the specified service has been resolved; otherwise, <c>false</c>.
        /// </returns>
        public bool CanResolve(Type service)
        {
            this.NotBeDisposed();
            Should.NotBeNull(service, "service");
            return _container.IsRegistered(service);
        }

        #endregion

        #region Implementation of IServiceProvider

        /// <summary>
        ///     Gets the service object of the specified type.
        /// </summary>
        /// <returns>
        ///     A service object of type <paramref name="serviceType" />.
        ///     -or-
        ///     null if there is no service object of type <paramref name="serviceType" />.
        /// </returns>
        /// <param name="serviceType">
        ///     An object that specifies the type of service object to get.
        /// </param>
        object IServiceProvider.GetService(Type serviceType)
        {
            return Get(serviceType);
        }

        #endregion
    }
}